OO_U4 - UML

本文使用Python搭建评测机。使用其他语言,在具体实现上可能存在差异,本文只是提供了一个思路。

前言

众所周知,本次作业采用了“交互式评测”,会根据同学们的程序输出,来动态生成新的指令。这对本地复现作业评测方式,构建自动化测试带来了一些挑战。

本人在一番折腾,并在各路大模型的协助后,成功在本地初步完成了交互式评测的搭建。本文将对我折腾的经验,进行一些简要的分享。

数据生成

正如评测说明,对于每一次评测,我们都有一套“基础输入”,取书/还书请求根据程序运行结果动态生成。

因此,我们采取如下的思路:

  • 数据生成器负责生成“基础输入”
  • 取书/还书请求由Checker根据程序运行结果,在基础输入中选取合适位置插入

Checker此时相当于承担了一部分数据生成的工作。

当然,为了方便复现测试结果,建议Checker输出一份stdin_actual,记录下插入了还书/取书请求的实际输入。

评测框架的修改

在前几单元中,我的评测机模式是这样,相信大家在这部分大同小异:

  • 评测机框架+数据生成器+Checker;
  • 数据生成器一次生成所有测试输入
  • Checker在待测程序完全运行后启动;通过比较待测程序完全运行后的结果,与Checker预期结果,得出待测程序是否正确的判断

在本单元的交互式评测模式下,类似第二单元中,使用数据投喂器的思路,我们:

  • 将Checker与待测程序同时运行
  • 用管道将二者的输入、输出连接起来
  • 待测程序的输入由Checker进行投放:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    java_process = subprocess.Popen(
    ['java', '-jar', os.path.join(target_dir, jar_file), f"-Xmx{memLimit}"],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=fileerr,
    )

    check_process = subprocess.Popen(
    ["python3", checker_path, out_file], # 注意参数out_file
    cwd=case_dir,
    stdin=java_process.stdout,
    stdout=java_process.stdin,
    stderr=logfile,
    )

    # 在完成数据投递/正确检查后,必须显式关闭java_process的stdin
    # 否则待测程序会认为仍有输入待读,一直等待,无法结束
    check_process.wait()
    java_process.stdin.close()

在上面的代码示例中,我的Checker通过读取工作目录下的stdin.txt,获取基础输入。因此,check_process中通过cwd指定了Checker的工作目录,以便进行多样例的并发评测。

进行自动化测试时,我们当然想要知道程序的输出;由于待测程序的输出管道接到了Checker的输入管道上,我们能想到的最美观办法,自然是由Checker记录待测程序的实际输出

Checker的修改

上面我们已经提到了这几个额外需求:

  • 动态生成return/pick指令,并插入到基础输入的特定位置
  • 记录实际输入
  • 记录程序输出

还有Checker本身要做的:

  • 交互式评测:向程序输入指令,同时动态获取一行或多行输出
  • 正确性检查

动态获取输出

这是Checker这几点中最麻烦的部分,主要在于多行输出的处理。

上面我们已经将Checker与待测程序的输入、输出管道连接在了一起。在Checker处,获取一行待测程序的输出,只需要sys.stdin.readline()即可。

那么,对于查询轨迹这种有多行输出的情况,是不是sys.stdin.readlines()就可以了呢?

很遗憾,是不行的。 因为readlines()是以EOF判断读取的结尾的,而EOF一般情况只在管道关闭时才能发送。那怎么办呢?

观察输出格式,不难发现,对于多行输出的情况,输出的第一行包含了我们接下来输出的行数

1
2
3
4
5
6
7
8
9
10
query:
[2025-01-07] B-0000-01 moving trace: **3**
1 [2025-01-06] from bs to ao
2 [2025-01-06] from ao to user
3 [2025-01-07] from user to bro

open:
**2**
[2025-01-06] move B-0000-01 from bro to bs
[2025-01-06] move C-0000-01 from bro to bs

因此,我们只需要想办法提取出接下来的行数”n”,随后n次readline() 即可。相信这也是课程组官方的交互式评测机处理多行输出的方法。

插入还/取书指令

上面提到,我们的OPEN/CLOSE等基础指令,是由数据生成器生成的。为了简化问题,我们规定,插入的还/取书操作,都在已有的OPEN/CLOSE间插入,不新开OPEN/CLOSE对。

在这个规定下,我们需要做这几件事情:

  • 读取生成的stdin.txt,得知所有OPEN/CLOSE对的时间
  • 写一个函数,对于一个成功的预约/借阅请求,取一个预约/借阅日之后的开馆日,作为插入还/取书指令的时间
  • 插入请求

对于如何插入请求,我的做法是这样 感谢D指导提供思路

  • 两个队列,队列A记录stdin.txt中的初始指令,队列B记录要插入的指令
  • 队列中元素是一个三元组: (date,priority,line),其中,OPEN、其他指令、CLOSE的优先级分别为0、1、2
  • 每次要插入指令时,把要插入的指令存在队列B中;随后,将两个队列合并,并依次按照日期、优先级的顺序重新排序
  • Checker从合并好的队列中,取一条指令,作为发送给待测程序的输入

如此我们就完成了指令的插入,剩下的事情就很简单了。

记录程序输出/实际输入

记录实际输入,只需要在向待测程序发送输入时,在stdin_actual里记下来即可。

同理,对于实际输出,读出一行,向out.txt里写一行就是。

下面仅作示意。

1
2
3
4
5
6
7
8
9
10
11
with open("./stdin_actual.txt", 'w') as actualStdin, \
open(stdin_file_path, 'r') as filein, \
open(out_path, 'w') as fileout:

for line in actual_lines:
actualStdin.write(line)
sys.stdout.write(line)
sys.stdout.flush() # flush()保证输入被立刻发送
...... # 下面仅作示意,多行输出的问题此处略去
line_out = sys.stdin.readline()
fileout.write(line_out + "\n")

正确性检查

这一部分,就可以像前几个单元一样,交给AI代劳了。

本人对Deepseek-R1,Gemini-2.5 Pro在内的多个模型进行测试,发现其均无法一下生成可用的交互式Checker(比如说,上面readlines()的问题,我没有特殊提示,AI很难一下处理好)。

这里建议,按照上面的步骤完成了输入/输出的处理后,再交给AI完成正确性检查的部分。

本次指导书的正确性约束较为分散,建议在给模型输入指导书时,在提示词里明确给出正确性约束条件,比如:

  • 借书/预约/取书成功的条件
  • 每次操作,书的运动轨迹:如借书时,从书架到用户

下面给出一个我用的prompt,供各位参考。需要注意,这个prompt我没有显式给出正确性约束条件,我得到的生成效果因此不大理想:

1
2
3
4
5
6
7
8
9
请按照content.json中的指导书,帮我修改本次作业的正确性检查器(checker.py)。

其中,你可能需要注意:

- 如我上传的checker.py所示,checker.py是一个交互式评测器;他将读取stdin.txt,并对输入对应的输出进行响应;我已经完成了输入与输出的交互读取,你需要编写对读出的输出,按指导书规定进行正确性检查的部分;

- 交互式评测会按照程序输出,生成对应的还书请求与取书请求;checker.py来完成这一部分工作。具体而言,当输出出现预约/借某本书成功后,checker.py将在预约/借书指令后的某一对OPEN/CLOSE指令之间,生成还书请求/取书请求。这样一来,生成的新请求,与stdin.txt中已有的请求是交错的。此外,你需要将包括了生成的还书请求/取书请求的实际输入,输出到checker.py留好的stdin_actual.txt中。

-checker.py的接口也应如示例中保留;我的checker.py已经完成了输入与输出的处理,你不需要对这部分进行修改;最后请输出一个完整的checker.py
作者

LajiPZ

发布于

2025-06-10

更新于

2025-07-08

许可协议

评论